﻿namespace SharpTalk.Core.Services
{
    using System;
    using System.Reflection;
    using System.Runtime.Remoting.Messaging;
    using System.ServiceModel;
    using Delegates;
    using Diagnostic;
    using Exceptions;
    using Extensions;
    using Interfaces;
    using Resources;

    /// <summary>
    /// Abstract CRUD type class
    /// </summary>
    /// <typeparam name="TCreateRequest">The type of the CreateRequest object.</typeparam>
    /// <typeparam name="TCreateResponse">The type of the CreateResponse object.</typeparam>
    /// <typeparam name="TFindRequest">The type of the FindRequest object.</typeparam>
    /// <typeparam name="TFindResponse">The type of the FindResponse object.</typeparam>
    /// <typeparam name="TUpdateRequest">The type of the UpdateRequest object.</typeparam>
    /// <typeparam name="TUpdateResponse">The type of the UpdateResponse object.</typeparam>
    /// <typeparam name="TDeleteRequest">The type of the DeleteRequest object.</typeparam>
    /// <typeparam name="TDeleteResponse">The type of the DeleteRequest object.</typeparam>
    public abstract class RequestServiceBase<
        TCreateRequest, TCreateResponse, 
        TFindRequest, TFindResponse,
        TUpdateRequest, TUpdateResponse,
        TDeleteRequest, TDeleteResponse> : Traceable
            where TCreateResponse : class, IErrorDetails, new()
            where TFindResponse   : class, IErrorDetails, new()
            where TUpdateResponse : class, IErrorDetails, new()
            where TDeleteResponse : class, IErrorDetails, new()
    {
        /// <summary>
        /// Checks if a request is valid. 
        /// </summary>
        /// <exception cref="T:SharpTalk.Core.Exceptions.RequestException"/>
        public virtual void CheckRequest<TRequest>(RequestBase<TRequest> request) 
            where TRequest : class
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            if (!request.IsValid())
                throw new RequestException(Messages.request_invalid);
        }
        public virtual void CheckRequest<TRequest>(RequestBase<TRequest> request, Func<bool> condition)
            where TRequest : class
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            if(!request.IsValid(condition))
                throw new RequestException(Messages.request_invalid);
        }
        /// <summary>
        /// Checks if the create can be executed
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <returns>True if the request is valid</returns>
        public virtual bool CanCreate(TCreateRequest request)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            return true;
        }
        /// <summary>
        /// Checks if the update  can be executed
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <returns>True if the request is valid</returns>
        public virtual bool CanUpdate(TUpdateRequest request)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            return true;
        }
        /// <summary>
        /// Checks if the delete can be executed
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <returns>True if the request is valid</returns>
        public virtual bool CanDelete(TDeleteRequest request)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            return true;
        }
        /// <summary>
        /// Checks if the find request can be executed
        /// </summary>
        /// <param name="request">The request to check</param>
        /// <returns>True if the request is valid</returns>
        public virtual bool CanFind(TFindRequest request)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            return true;
        }
        /// <summary>
        /// Executes before calling OnCreate
        /// </summary>
        /// <param name="request">The request received</param>
        /// <returns>The response returned by calling OnCreate</returns>
        public virtual TCreateResponse BeforeCreate(TCreateRequest request)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            TCreateResponse response;
            try
            {
                if (!CanCreate(request)) throw new CreateRequestException();
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                response = new TCreateResponse(){ Error  = new ErrorDetails(exp) };
                return response;
            }
            try
            {
                response = Create(request);
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                response = new TCreateResponse() { Error = new ErrorDetails(exp) };
                return response;
            }
            try
            {
                AfterCreate(response);
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                if (response == null)
                    response = new TCreateResponse() { Error = new ErrorDetails(exp) };
                else
                    response.Error = new ErrorDetails(exp);
            }
            return response;
        }
        /// <summary>
        /// Executes before calling OnUpdate
        /// </summary>
        /// <param name="request">The request received</param>
        /// <returns>The response returned by calling OnUpdate</returns>
        public virtual TUpdateResponse BeforeUpdate(TUpdateRequest request)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            TUpdateResponse response;
            try
            {
                if (!CanUpdate(request)) throw new UpdateRequestException();
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                response = new TUpdateResponse() { Error = new ErrorDetails(exp) };
                return response;
            }
            try
            {
                response = Update(request);
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                response = new TUpdateResponse() { Error = new ErrorDetails(exp) };
                return response;
            }
            try
            {
                AfterUpdate(response);
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                if (response == null)
                    response = new TUpdateResponse() { Error = new ErrorDetails(exp) };
                else
                    response.Error = new ErrorDetails(exp);
            }
            return response;
        }
        /// <summary>
        /// Executes before calling OnDelete
        /// </summary>
        /// <param name="request">The request received</param>
        /// <returns>The response returned by calling OnDelete</returns>
        public virtual TDeleteResponse BeforeDelete(TDeleteRequest request)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            TDeleteResponse response;
            try
            {
                if (!CanDelete(request)) throw new DeleteRequestException();
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                response = new TDeleteResponse() { Error = new ErrorDetails(exp) };
                return response;
            }
            try
            {
                response = Delete(request);
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                response = new TDeleteResponse() { Error = new ErrorDetails(exp) };
                return response;
            }
            try
            {
                AfterDelete(response);
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                if (response == null)
                    response = new TDeleteResponse() { Error = new ErrorDetails(exp) };
                else
                    response.Error = new ErrorDetails(exp);
            }
            return response;
        }
        /// <summary>
        /// Executes before calling OnFind
        /// </summary>
        /// <param name="request">The request received</param>
        /// <returns>The response returned by calling OnFind</returns>
        public virtual TFindResponse BeforeFind(TFindRequest request)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            TFindResponse response;
            try
            {
                if (!CanFind(request)) throw new FindRequestException();
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                response = new TFindResponse() { Error = new ErrorDetails(exp) };
                return response;
            }
            try
            {
                response = Find(request);
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(), exp);
                response = new TFindResponse() { Error = new ErrorDetails(exp) };
                return response;
            }
            try
            {
                AfterFind(response);
            }
            catch (Exception exp)
            {
                TraceException(MethodBase.GetCurrentMethod(),exp);
                if (response == null)
                    response = new TFindResponse() { Error = new ErrorDetails(exp) };
                else
                    response.Error = new ErrorDetails(exp);
            }
            return response;
        }
        /// <summary>
        /// Executes after calling OnCreate
        /// </summary>
        /// <param name="response">The response returned by calling OnCreate</param>
        public virtual void AfterCreate(TCreateResponse response) { TraceMethodCall(MethodBase.GetCurrentMethod()); }
        /// <summary>
        /// Executes after calling OnFind
        /// </summary>
        /// <param name="response">The response returned by calling OnFind</param>
        public virtual void AfterFind(TFindResponse response) { TraceMethodCall(MethodBase.GetCurrentMethod()); }
        /// <summary>
        /// Executes after calling OnUpdate
        /// </summary>
        /// <param name="response">The response returned by calling OnUpdate</param>
        public virtual void AfterUpdate(TUpdateResponse response) { TraceMethodCall(MethodBase.GetCurrentMethod()); }
        /// <summary>
        /// Executes after calling OnDelete
        /// </summary>
        /// <param name="response">The response returned by OnDelete</param>
        public virtual void AfterDelete(TDeleteResponse response) { TraceMethodCall(MethodBase.GetCurrentMethod()); }
        [OperationBehavior]
        public virtual IAsyncResult BeginCreate(TCreateRequest request, AsyncCallback callback, object state)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            var del = new RequestDelegate<TCreateRequest, TCreateResponse>(BeforeCreate);
            return del.BeginInvoke(request, callback, state);
        }
        public virtual TCreateResponse EndCreate(IAsyncResult result)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            var res = (AsyncResult)result;
            var response = ((RequestDelegate<TCreateRequest, TCreateResponse>) res.AsyncDelegate).EndInvoke(result);
            res.AsyncWaitHandle.Close();
            return response;
        }
        [OperationBehavior]
        public virtual IAsyncResult BeginFind(TFindRequest request, AsyncCallback callback, object state)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            var del = new RequestDelegate<TFindRequest, TFindResponse>(BeforeFind);
            return del.BeginInvoke(request, callback, state);
        }
        public virtual TFindResponse EndFind(IAsyncResult result)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            var res = (AsyncResult)result;
            var response = ((RequestDelegate<TFindRequest, TFindResponse>)res.AsyncDelegate).EndInvoke(result);
            res.AsyncWaitHandle.Close();
            return response;
        }
        [OperationBehavior]
        public virtual IAsyncResult BeginUpdate(TUpdateRequest request, AsyncCallback callback, object state)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            var del = new RequestDelegate<TUpdateRequest, TUpdateResponse>(BeforeUpdate);
            return del.BeginInvoke(request, callback, state);
        }
        public virtual TUpdateResponse EndUpdate(IAsyncResult result)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            var res = (AsyncResult)result;
            var response = ((RequestDelegate<TUpdateRequest, TUpdateResponse>)res.AsyncDelegate).EndInvoke(result);
            res.AsyncWaitHandle.Close();
            return response;
        }
        [OperationBehavior]
        public virtual IAsyncResult BeginDelete(TDeleteRequest request, AsyncCallback callback, object state)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            var del = new RequestDelegate<TDeleteRequest, TDeleteResponse>(BeforeDelete);
            return del.BeginInvoke(request, callback, state);
        }
        public virtual TDeleteResponse EndDelete(IAsyncResult result)
        {
            TraceMethodCall(MethodBase.GetCurrentMethod());
            var res = (AsyncResult)result;
            var response = ((RequestDelegate<TDeleteRequest, TDeleteResponse>)res.AsyncDelegate).EndInvoke(result);
            res.AsyncWaitHandle.Close();
            return response;
        }

        public abstract TCreateResponse Create(TCreateRequest request);
        public abstract TFindResponse Find(TFindRequest request);
        public abstract TUpdateResponse Update(TUpdateRequest request);
        public abstract TDeleteResponse Delete(TDeleteRequest request);
    }
}